module Stone
  class Lex
    class Token < Struct.new(:lineno, :offset, :value)
      def to_s 
        value.to_s
      end

      def number?     = false
      def identifier? = false
      def string?     = false
    end

    class NumToken < Token 
      def number? = true
    end

    class IdenToken < Token
      def identifier? = true
    end

    class StrToken < Token 
      def string? = true
    end

    EOL = "\\n"
    EOF = Token.new(-1, -1, -1)

    # names regexp todo
    SPACE  = /\s*/
    COMM   = /#.*/
    NUM    = /\d+/
    IDEN   = /[a-zA-Z_]\w*/
    STR    = // # todo
    OP     = /==|<|>|<=|>=|&&|\+|=/

    PAT = /(#{SPACE})(#{COMM}|#{NUM}|#{IDEN}|#{OP}|\p{Punct})?/

    def initialize(reader)
      @reader = reader
      @tokens = []
      @more   = true
    end

    def read
      @tokens.shift
    end

    def peek(o = 0)
      if fill(o)
        @tokens[o]
      else
        EOF
      end
    end

    def fill(o)
      while o > @tokens.size || @tokens.empty?
        if @more
          read_line
        else
          return false
        end
      end

      true
    end

    def read_line
      lineno = @reader.lineno
      line   = @reader.gets
      if line.nil? 
        @more = false
        return 
      end
      size   = line.size 

      # todo exception handing : when matching failured
      # todo some bugs lead to a infinite loop when a line includes 
      # unmatching oprators like '<' (DONE)
      # todo offset compute wrong because of the spaces that can't been 
      # captured (DONE)
      offset = 0
      while offset < size
        match = PAT.match line, offset
        add_token match[2], lineno, offset + match[1].size
        offset += match[0].size
      end

      @tokens.push IdenToken.new(lineno, offset, EOL)
    end

    def add_token(match, lineno, offset)
      return if match in Lex::COMM 
      return if match == nil

      token = case match
              when Lex::NUM
                Lex::NumToken.new(lineno, offset, match.to_i)
              else 
                Lex::IdenToken.new(lineno, offset, match)
              end

      @tokens.push token
    end
  end

end
